#include "preHeader.h"
#include "Player.h"
#include "Map/MapInstance.h"
#include "Quest/QuestMgr.h"
#include "Session/DBPServerMgr.h"
#include "Session/GameServerMgr.h"
#include "jsontable/table_helper.h"
#include "Spell/SpellMgr.h"

void Player::SaveCharacterAsync()
{
	if (!m_isInitFinished) {
		return;
	}

	m_ipcInfo.ipcLastOnlineTime = GET_UNIX_TIME;

	m_ipcInfo.ipcCareer = GetS32Value(PLAYER_FIELD_CAREER);
	m_ipcInfo.ipcGender = GetS32Value(PLAYER_FIELD_GENDER);
	m_ipcInfo.ipcLevel = GetS32Value(UNIT_FIELD_LEVEL);
	m_ipcInfo.ipcExp = GetS64Value(PLAYER64_FIELD_EXP);
	m_ipcInfo.ipcCurHP = GetS64Value(UNIT64_FIELD_HP);
	m_ipcInfo.ipcCurMP = GetS64Value(UNIT64_FIELD_MP);
	m_ipcInfo.ipcMoneyGold = GetS64Value(S64CURRENCY(CurrencyType::Gold));
	m_ipcInfo.ipcMoneyDiamond = GetS64Value(S64CURRENCY(CurrencyType::Diamond));

	m_ipcInfo.ipcPropertyValue.bagCapacity = m_pItemStorage->GetBagCapacity();
	m_ipcInfo.ipcPropertyValue.bankCapacity = m_pItemStorage->GetBankCapacity();
	m_ipcInfo.ipcEffectItems = m_pItemStorage->SaveEffectItems();
	m_ipcInfo.ipcAllOtherItems = m_pItemStorage->SaveAllOtherItems();
	m_ipcInfo.ipcStorageStatus = m_pItemStorage->SaveStorageStatus();

	m_ipcInfo.ipcQuestsDone = m_pQuestStorage->SaveQuestsDone();
	m_ipcInfo.ipcQuests = m_pQuestStorage->SaveQuests();

	m_ipcInfo.ipcVipStatus = m_pVipStorage->Save();

	m_ipcInfo.ipcShopStatus = SaveShopStatus();
	m_ipcInfo.ipcSpells = SaveSpells();
	m_ipcInfo.ipcBuffs = SaveBuffs();
	m_ipcInfo.ipcCooldowns = SaveCooldowns();

	m_ipcInfo.ipcJsonValue = SaveJsonValue();

	m_ipcInfo.ipcF32Values.resize(IPCF32_MAX);
	std::copy(std::begin(m_ipcF32Values),
		std::end(m_ipcF32Values), m_ipcInfo.ipcF32Values.begin());
	m_ipcInfo.ipcS32Values.resize(IPCS32_MAX);
	std::copy(std::begin(m_ipcS32Values),
		std::end(m_ipcS32Values), m_ipcInfo.ipcS32Values.begin());
	m_ipcInfo.ipcS64Values.resize(IPCS64_MAX);
	std::copy(std::begin(m_ipcS64Values),
		std::end(m_ipcS64Values), m_ipcInfo.ipcS64Values.begin());

	if (m_pMapInstance->IsSavePlayerPosition()) {
		m_ipcInfo.ipcMapType = GetMapType();
		m_ipcInfo.ipcMapID = GetMapId();
		m_ipcInfo.ipcPosX = GetPosition().x;
		m_ipcInfo.ipcPosY = GetPosition().y;
		m_ipcInfo.ipcPosZ = GetPosition().z;
		m_ipcInfo.ipcPosO = GetOrientation();
	}

	MaxNetPacket rpcReqPck(CDBP_SAVE_ONE_PLAYER_INSTANCE);
	SaveToINetStream(m_ipcInfo, rpcReqPck);
	DBPSession(GetGsId()).RPCInvoke(rpcReqPck, [=](INetStream& pck, int32 err, bool) {
		if (!IsAlive()) {
			NetPacket notify(MS_CHARACTER_SAVED);
			notify << GetGuid4Gs() << err;
			GSSession(GetGsId()).PushSendPacket(notify);
		}
	}, this, DEF_S2S_RPC_TIMEOUT);
}

void Player::LoadCharacter(const inst_player_char& info)
{
	m_ipcInfo = info;

	SetS32Value(PLAYER_FIELD_CAREER, info.ipcCareer);
	SetS32Value(PLAYER_FIELD_GENDER, info.ipcGender);
	SetS32Value(UNIT_FIELD_LEVEL, info.ipcLevel);
	SetS64Value(PLAYER64_FIELD_EXP, info.ipcExp);
	SetS64Value(PLAYER64_FIELD_LVUP_EXP, GetLevelUpExp());
	SetS64Value(UNIT64_FIELD_HP, info.ipcCurHP);
	SetS64Value(UNIT64_FIELD_MP, info.ipcCurMP);
	SetS64Value(S64CURRENCY(CurrencyType::Gold), info.ipcMoneyGold);
	SetS64Value(S64CURRENCY(CurrencyType::Diamond), info.ipcMoneyDiamond);

	m_pItemStorage->SetBagCapacity(info.ipcPropertyValue.bagCapacity);
	m_pItemStorage->SetBankCapacity(info.ipcPropertyValue.bankCapacity);
	m_pItemStorage->LoadEffectItems(info.ipcEffectItems);
	m_pItemStorage->LoadAllOtherItems(info.ipcAllOtherItems);
	m_pItemStorage->LoadStorageStatus(info.ipcStorageStatus);

	m_pQuestStorage->LoadQuestsDone(m_ipcInfo.ipcQuestsDone);
	m_pQuestStorage->LoadQuests(m_ipcInfo.ipcQuests);

	m_pVipStorage->Load(m_ipcInfo.ipcVipStatus);

	LoadShopStatus(m_ipcInfo.ipcShopStatus);
	LoadCooldowns(m_ipcInfo.ipcCooldowns);

	LoadJsonValue(info.ipcJsonValue);

	std::copy_n(m_ipcInfo.ipcF32Values.begin(), std::min<size_t>
		(m_ipcInfo.ipcF32Values.size(), IPCF32_MAX), m_ipcF32Values);
	std::copy_n(m_ipcInfo.ipcS32Values.begin(), std::min<size_t>
		(m_ipcInfo.ipcS32Values.size(), IPCS32_MAX), m_ipcS32Values);
	std::copy_n(m_ipcInfo.ipcS64Values.begin(), std::min<size_t>
		(m_ipcInfo.ipcS64Values.size(), IPCS64_MAX), m_ipcS64Values);
}

void Player::LoadPlayerTeleportInfo(const PlayerTeleportInfo& info)
{
	m_tpInfo = info;

	SetPosition({info.x, info.y, info.z});
	SetOrientation(info.o);

	ApplyTeleportFlagsOutOfWorld(m_tpInfo.flags);

	if (info.type == (u32)TeleportType::Login) {
		m_ipcInfo.ipcLastLoginTime = GET_UNIX_TIME;
	}
}

void Player::InitPlayer(ObjGUID playerGuid,
	const inst_player_char& ipcInfo, const PlayerTeleportInfo& tpInfo)
{
	SetGuid(playerGuid);
	LoadCharacter(ipcInfo);
	LoadPlayerTeleportInfo(tpInfo);
	if (!Init(ipcInfo.ipcAppearance.charTypeId)) {
		WLOG("Init player(%hu,%u) failed!", playerGuid.SID, playerGuid.UID);
	}
}

std::string Player::SaveShopStatus() const
{
	TextPacker packer;
	for (auto&[spId, siStatus] : m_shopStatus) {
		packer << spId << siStatus.dailyCount << siStatus.weeklyCount;
		packer.PutDelimiter(';');
	}
	packer.PutDelimiter('\n');
	for (auto&[itemUniqueKey, ssStatus] : m_specialShopStatus) {
		packer << itemUniqueKey << ssStatus.dailyCount << ssStatus.weeklyCount
			<< ssStatus.totalCount << ssStatus.expireTime;
		packer.PutDelimiter(';');
	}
	packer.PutDelimiter('\n');
	return packer.str();
}

void Player::LoadShopStatus(const std::string& data)
{
	TextUnpacker unpacker(data.c_str());
	if (!unpacker.IsEmpty()) {
		do {
			uint32 spId;
			ShopItemStatus siStatus;
			unpacker >> spId >> siStatus.dailyCount >> siStatus.weeklyCount;
			m_shopStatus.emplace(spId, siStatus);
		} while (!unpacker.IsEmpty() && unpacker.IsDelimiter(';'));
	}
	if (!unpacker.IsEmpty()) {
		do {
			uint64 itemUniqueKey;
			ShopItemStatus ssStatus;
			unpacker >> itemUniqueKey >> ssStatus.dailyCount >> ssStatus.weeklyCount
				>> ssStatus.totalCount >> ssStatus.expireTime;
			m_specialShopStatus.emplace(itemUniqueKey, ssStatus);
		} while (!unpacker.IsEmpty() && unpacker.IsDelimiter(';'));
	}
}

std::string Player::SaveSpells() const
{
	TextPacker packer;
	for (auto&[key, spellPropInfo] : m_spellPropList) {
		packer << spellPropInfo.pSpellProto->siInfo->spellID
			<< spellPropInfo.spellLevel << spellPropInfo.flags;
		packer.PutDelimiter(';');
	}
	return packer.str();
}

void Player::LoadSpells(const std::string& data)
{
	TextUnpacker unpacker(data.c_str());
	while (!unpacker.IsEmpty()) {
		uint32 spellID, spellLevel, flags;
		unpacker >> spellID >> spellLevel >> flags;
		if (!LearnSpell(spellID, spellLevel, flags)) {
			WLOG("Player[%hu,%u] load spell[%u,%u] failed.",
				m_guid.SID, m_guid.UID, spellID, spellLevel);
		}
	}
}

std::string Player::SaveBuffs() const
{
	TextPacker packer;
	for (auto&[key, pSpellBuffInfo] : m_spellBuffInfos) {
		auto siInfo = pSpellBuffInfo->pSpellProto->siInfo;
		switch (SpellResumeType(siInfo->spellBuffType)) {
		case SpellResumeType::RemoveWhenOffline:
			continue;
		}
		auto t = pSpellBuffInfo->t.Get<LuaTable>();
		if (!t.get<bool>("resumable")) {
			continue;
		}
		packer << siInfo->spellID << pSpellBuffInfo->spellLevel
			<< pSpellBuffInfo->effectIndex
			<< GET_SYS_TIME - pSpellBuffInfo->start;
		auto retVals = t.get<bool>("SaveArgs") ?
			LuaFuncs(t).XCallMethod("SaveArgs") : LuaFifoTopValues{};
		packer << (retVals.nvalues() > 0 ?
			retVals.GetValue<std::string_view>() : emptyStringView);
		packer.PutDelimiter(';');
	}
	return packer.str();
}

void Player::LoadBuffs(const std::string& data)
{
	TextUnpacker unpacker(data.c_str());
	auto skipTime = SubLeastZero<s64>(
		GET_UNIX_TIME, m_ipcInfo.ipcLastOnlineTime) * 1000llu;
	while (!unpacker.IsEmpty()) {
		uint32 spellID, spellLevel, effectIndex;
		uint64 elapseTime;
		std::string_view buffArgs;
		unpacker >> spellID >> spellLevel >> effectIndex
			>> elapseTime >> buffArgs;
		auto errCode = Spell::ResumeSpell(this, spellID,
			spellLevel, effectIndex, elapseTime, skipTime, buffArgs);
		if (errCode != CommonSuccess) {
			WLOG("Player[%hu,%u] load spell[%u,%u,%u] failed.",
				m_guid.SID, m_guid.UID, spellID, spellLevel, effectIndex);
		}
	}
}

std::string Player::SaveCooldowns() const
{
	TextPacker packer;
	for (size_t i = 0; i < ARRAY_SIZE(m_cooldownMap); ++i) {
		for (auto&[Key, cooldownInfo] : m_cooldownMap[i]) {
			auto siInfo = cooldownInfo.pSpellProto->siInfo;
			switch (SpellResumeType(siInfo->spellCooldownType)) {
			case SpellResumeType::RemoveWhenOffline:
				continue;
			}
			packer << i << Key << siInfo->spellID << cooldownInfo.RecoveryTime
				<< SubLeastZero(cooldownInfo.ExpireTime, GET_SYS_TIME);
			packer.PutDelimiter(';');
		}
	}
	return packer.str();
}

void Player::LoadCooldowns(const std::string& data)
{
	TextUnpacker unpacker(data.c_str());
	auto skipTime = SubLeastZero<s64>(
		GET_UNIX_TIME, m_ipcInfo.ipcLastOnlineTime) * 1000llu;
	while (!unpacker.IsEmpty()) {
		CooldownType Type;
		uint32 Key, SpellID;
		uint64 CooldownRemain, CooldownMax;
		unpacker >> Type >> Key >> SpellID >> CooldownRemain >> CooldownMax;
		auto pSpellProto = sSpellMgr.GetSpellPrototype(SpellID);
		if (pSpellProto == NULL) {
			continue;
		}
		switch (SpellResumeType(pSpellProto->siInfo->spellCooldownType)) {
		case SpellResumeType::RemoveWhenOffline:
			CooldownRemain = 0;
			break;
		case SpellResumeType::ContinueWhenOnline:
			break;
		case SpellResumeType::ContinueByTime:
			SubLeastZeroX(CooldownRemain, skipTime);
			break;
		}
		if (CooldownRemain == 0) {
			continue;
		}
		_Cooldown_Add_Raw(pSpellProto, Type, Key, CooldownRemain, CooldownMax);
	}
}

std::string Player::SaveJsonValue() const
{
	rapidjson::Document document;
	document.SetObject();
	#define AddJsonMember(name, value) funcAddJsonMember \
		(rapidjson::Value(rapidjson::StringRef(name)), rapidjson::Value(value))
	auto funcAddJsonMember = [&](rapidjson::Value&& name, rapidjson::Value&& value) {
		document.AddMember(std::move(name), std::move(value), document.GetAllocator());
	};
	auto JsonValue = [&](const std::string &value) {
		return rapidjson::Value(
			value.data(), rapidjson::SizeType(value.size()), document.GetAllocator());
	};

	TextPacker packer;
	packer << m_itemUniqueKey << m_questUniqueKey;
	AddJsonMember("XXXUniqueKey", JsonValue(packer.str()));

	packer.str(emptyString);
	packer << m_lastNewDailyTime << m_lastNewWeeklyTime << m_lastNewMonthlyTime;
	AddJsonMember("LastNewXXXTime", JsonValue(packer.str()));

	packer.str(emptyString);
	packer << m_lastBackInstGuid.TID << m_lastBackInstGuid.MAPID;
	packer << m_lastBackPos.x << m_lastBackPos.y << m_lastBackPos.z << m_lastBackPos.o;
	AddJsonMember("LastBackPosition", JsonValue(packer.str()));

	AddJsonMember("InvisibleCreatures", JsonValue(SaveInvisibleCreatures()));
	AddJsonMember("ConvoyStatus", JsonValue(SaveAllConvoyStatus()));

	rapidjson::StringBuffer buffer;
	rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
	document.Accept(writer);
	return std::string(buffer.GetString(), buffer.GetSize());
}

void Player::LoadJsonValue(const std::string& data)
{
	rapidjson::Document document;
	document.Parse(data.c_str(), data.size());
	if (!document.IsObject()) {
		return;
	}

	auto itr = document.FindMember("XXXUniqueKey");
	if (itr != document.MemberEnd() && itr->value.IsString()) {
		TextUnpacker unpacker(itr->value.GetString());
		unpacker >> m_itemUniqueKey >> m_questUniqueKey;
	}

	itr = document.FindMember("LastXXXTime");
	if (itr != document.MemberEnd() && itr->value.IsString()) {
		TextUnpacker unpacker(itr->value.GetString());
		unpacker >> m_lastNewDailyTime >> m_lastNewWeeklyTime >> m_lastNewMonthlyTime;
	}


	itr = document.FindMember("LastBackPosition");
	if (itr != document.MemberEnd() && itr->value.IsString()) {
		TextUnpacker unpacker(itr->value.GetString());
		m_lastBackInstGuid.UID = INST_UID_INVALID;
		unpacker >> m_lastBackInstGuid.TID >> m_lastBackInstGuid.MAPID;
		unpacker >> m_lastBackPos.x >> m_lastBackPos.y >> m_lastBackPos.z >> m_lastBackPos.o;
	}

	itr = document.FindMember("InvisibleCreatures");
	if (itr != document.MemberEnd() && itr->value.IsString()) {
		LoadInvisibleCreatures(itr->value.GetString());
	}

	itr = document.FindMember("ConvoyStatus");
	if (itr != document.MemberEnd() && itr->value.IsString()) {
		LoadAllConvoyStatus(itr->value.GetString());
	}
}

void Player::TryInitNewPlayer()
{
	if (unlikely(!m_ipcInfo.ipcFlags.isInit)) {
		m_ipcInfo.ipcFlags.isInit = true;
		static const std::string scriptFile = "scripts/Player/InitNewPlayer.lua";
		RunScriptFile(m_pMapInstance->L, scriptFile, this, GetCareer(), GetGender());
	}
}

void Player::OnFirstPushToWorld()
{
	CreateTimerX(std::bind(&Player::SaveCharacterAsync, this), 5*60*1000);
	CreateTimerX(std::bind(&Player::SendCharacterInfo, this), 3*1000);

	m_pQuestStorage->PostInitAndUpdateQuests();
	sQuestMgr.InitAllQuestWatchStatus(this);

	LoadSpells(m_ipcInfo.ipcSpells);
	LoadBuffs(m_ipcInfo.ipcBuffs);

	ApplyTeleportFlagsInWorld(m_tpInfo.flags);

	RestoreAllConvoyStatus();

	if (m_tpInfo.teamId != 0) {
		m_pMapInstance->GetMapTeamManager().SetTeamOrPullTeam(this, m_tpInfo.teamId);
	}
}

void Player::OnFinishInMapState()
{
	auto lastDayTime = GET_DAY_UNIX_TIME;
	if (m_lastNewDailyTime < lastDayTime) {
		OnNewDaily(true);
	}
	auto lastWeekTime = GET_WEEK_UNIX_TIME;
	if (m_lastNewWeeklyTime < lastWeekTime) {
		OnNewWeekly(true);
	}
	auto lastMonthTime = GET_MONTH_UNIX_TIME;
	if (m_lastNewMonthlyTime < lastMonthTime) {
		OnNewMonthly(true);
	}
}

void Player::OnNewDaily(bool isEnterMap)
{
	m_lastNewDailyTime = GET_UNIX_TIME;
	m_pQuestStorage->EventCleanRepeatQuests(QuestRepeatType::Daily);
}

void Player::OnNewWeekly(bool isEnterMap)
{
	m_lastNewWeeklyTime = GET_UNIX_TIME;
	m_pQuestStorage->EventCleanRepeatQuests(QuestRepeatType::Weekly);
}

void Player::OnNewMonthly(bool isEnterMap)
{
	m_lastNewMonthlyTime = GET_UNIX_TIME;
	m_pQuestStorage->EventCleanRepeatQuests(QuestRepeatType::Monthly);
}

void Player::SendCharacterInfo()
{
	NetPacket pack(MS_UPDATE_CHARACTER_INFO);
	pack << GetGuid4Gs();
	auto anchor = pack.GetWritePos();

	CBitMask& bitMask = m_pMapInstance->sCharUpdateInfoBitMask;
	bitMask.Reset();
	bitMask.Write(pack);

	auto vipLevel = m_pVipStorage->GetLevel();
	if (m_bakCharInfo.lastVipLevel != vipLevel) {
		m_bakCharInfo.lastVipLevel = vipLevel;
		pack << m_bakCharInfo.lastVipLevel;
		bitMask.SetDirty((int)CharUpdateInfo::Type::VipLevel);
	}

	auto level = GetLevel();
	if (m_bakCharInfo.lastLevel != level) {
		m_bakCharInfo.lastLevel = level;
		pack << m_bakCharInfo.lastLevel
			<< m_ipcInfo.ipcRankValue.lastLevelTime;
		bitMask.SetDirty((int)CharUpdateInfo::Type::Level);
	}

	auto hpRate = GetHPRate();
	if (std::abs(m_bakCharInfo.lastPercHP - hpRate) > 1e-3f) {
		m_bakCharInfo.lastPercHP = hpRate;
		pack << m_bakCharInfo.lastPercHP;
		bitMask.SetDirty((int)CharUpdateInfo::Type::PercHP);
	}

	auto mpRate = GetMPRate();
	if (std::abs(m_bakCharInfo.lastPercMP - mpRate) > 1e-3f) {
		m_bakCharInfo.lastPercMP = mpRate;
		pack << m_bakCharInfo.lastPercMP;
		bitMask.SetDirty((int)CharUpdateInfo::Type::PercMP);
	}

	auto o = GetOrientation();
	if (m_bakCharInfo.lastPos.xyz().DistanceSq(GetPosition()) > 1.f ||
		std::abs(m_bakCharInfo.lastPos.o - o) > .1f) {
		m_bakCharInfo.lastPos.xyz(GetPosition());
		m_bakCharInfo.lastPos.o = o;
		pack << m_bakCharInfo.lastPos;
		bitMask.SetDirty((int)CharUpdateInfo::Type::Pos);
	}

	auto fightValue = GetMaxFightValue();
	if (m_bakCharInfo.lastFightValue != fightValue) {
		m_bakCharInfo.lastFightValue = fightValue;
		pack << m_bakCharInfo.lastFightValue
			<< m_ipcInfo.ipcRankValue.lastFightValueTime;
		bitMask.SetDirty((int)CharUpdateInfo::Type::FightValue);
	}

	if (!bitMask.IsDirty()) {
		return;
	}

	auto totalSize = pack.GetTotalSize();
	pack.Shrink(anchor);
	bitMask.Write(pack);
	pack.Erlarge(totalSize);
	SendPacket2Gs(pack);
}
